/* 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

Title : com.Angaliwa.ResizeHandler
Author : Dave Jansen -- Zenitum
URL : http://www.zenitum.com/

Description : This module allows you to easily set the size
			  and position of objects that are not possible
			  otherwise, from percentage-based sizes to 
			  making items 'stick' to a specific side. 
			  Requires Angaliwa's StageHandler, DaisyChain
			  and Settings modules (or equivalent). 

History		: v1.0.090827 Initial version

Created 	: August 27th, 2009

Version		: 1.0
Compatibility: 1.0

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
*/

package com.Angaliwa{
	import flash.display.DisplayObject;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.TimerEvent;
	import flash.utils.Timer;
	
	public class ResizeHandler{
		/*
			Available daisyChain actions:
			- startResizing
			- resizing
			- probablyDoneResizing
		*/
		public var daisyChain:DaisyChain = new DaisyChain(this),
				   stageHandler:StageHandler = Core.g('stageHandler'),
				   settings:Settings = new Settings();
		
		private var resizing:Boolean=false,
					t:Timer,
					sizes:Array = [],
					positions:Array = [];
		
		/*
		 * Summary:      	Initializes the ResizeHandler
		 * Return:       	Void
		 * Version:   	 	1.0
		 * Compatibility:	1.0
		 */
		public function ResizeHandler(stageHandler:StageHandler=undefined, customSettings:Object=undefined){
			if(stageHandler)
				this.stageHandler = stageHandler;
			
			// Set up the default settings and
			// custom settings, if available.
			settings.setDefaults({
						stageAlign: StageAlign.TOP_LEFT,
						stageScaleMode: StageScaleMode.NO_SCALE,
						resizedTriggerTimeout: 300
					})
					.add(customSettings);
			
			// Set up the event listener and the such so that
			// ResizeHandler can properly respond based on 
			// what the user is doing (or did).
			this.stageHandler.add(Event.RESIZE, eventListener);
		}
		
		/*
		 * Summary:      	The method triggered when the application
		 					is being resized.
		 * Return:       	Void
		 * Version:   	 	1.0
		 * Compatibility:	1.0
		 */
		public function eventListener(event:Event=undefined):void{
			if(!resizing){
				resizing=true;
				daisyChain.run('startResizing');
			}
			
			daisyChain.run('resizing');
			
			// If a previous timer was set, clear it.
			if(t) t.stop();
			
			// By working with a small timeout, we can 
			// assume that once the timer triggers, the
			// user stopped resizing the window. This way
			// a low-end computer won't drag to a halt
			// when the user is trying to resize the window
			if(settings.get('resizedTriggerTimeout')>0){
				t = new Timer(settings.get('resizedTriggerTimeout'));
				t.addEventListener(TimerEvent.TIMER, resized);
				t.start();
			}
			else resized();
		}
		
		private function resized(timerEvent:TimerEvent=undefined):void{
			daisyChain.run('probablyDoneResizing');
			resizing=false;
			
			// If the function was triggered by a timer,
			// clear it.
			if(t) t.stop();
			
			// Loop through all objects that have 
			// sizing and/or positioning settings set.
			sizes.forEach(function(item:Object, index:int, arr:Array):void{
				setSize(item.obj, item.width, item.height);
			});
			
			positions.forEach(function(item:Object, index:int, arr:Array):void{
				setPosition(item.obj, item.horizontal, item.vertical);
			});
		}
		
		/*
		 * Summary:      	Sets the size for the specified object and
		 					adds this setting to the eventHandler.
		 * Return:       	ResizeHandler (this)
		 * Version:   	 	1.0
		 * Compatibility:	1.0
		 */
		public function setSize(obj:DisplayObject, width:String=undefined, height:String=undefined):ResizeHandler{
			if(obj.parent){
				// Make sure all parent objects of this object are properly
				// resized before we resize this one, else some artifacts or
				// improperly sized elements might appear.
				sizes.some(function(item:Object, index:int, arr:Array):Boolean{
					if(item.obj == obj.parent){
						setSize(item.obj, item.width, item.height);
						return true;
					}
					
					return false;
				});
				
				
				if(width)
					obj.width = gen(width, obj.parent.width);
				
				if(height)
					obj.height = gen(height, obj.parent.height);
				
				addToList(sizes, obj, {width:width,height:height});
			}
			
			return this;
		}
		
		public function setPosition(obj:DisplayObject, horizontal:String=undefined, vertical:String=undefined):ResizeHandler{
			if(obj.parent){
				// Make sure all parent objects of this object are properly
				// positioned before we position this one, else some properly
				// positioned items might appear.
				positions.some(function(item:Object, index:int, arr:Array):Boolean{
					if(item.obj == obj.parent){
						setPosition(item.obj, item.horizontal, item.vertical);
						return true;
					}
					
					return false;
				});
				
				if(horizontal)
					obj.x = gen(horizontal, obj.parent.width-obj.width);
				
				if(vertical)
					obj.y = gen(vertical, obj.parent.height-obj.height);
				
				addToList(positions, obj, {horizontal:horizontal,vertical:vertical});
			}
			
			return this;
		}
		
		private function gen(size:String,max:int,min:int=undefined):int{
			var pos:int = 0,
				arr:Array = new Array(),
				tmp:Array,
				i:int = 0;
			
			if(size.indexOf('+')>-1){
				tmp = size.split('+');
				arr = arr.concat(tmp);
				for(i=1; i < tmp.length; i++)
					pos += num(tmp[i],max);
			}
			
			if(size.indexOf('-')>-1){
				tmp = size.split('-');
				arr = arr.concat(tmp);
				for(i=1; i < tmp.length; i++)
					pos -= num(tmp[i],max);
			}
			
			if(arr.length==0)
				pos = num(size,max);
			else
				pos += num(arr[0],max);
			
			return pos;
		}
		
		private function num(n:String, max:int):int{
			switch(n){
				case "left":
				case "top":
					return 0;
				break;
				case "right":
				case "bottom":
					return max;
				break;
				case "middle":
				case "center":
				case "half":
					return max/2;
				break;
				default:
					if(n.indexOf("%")>0)
						return (max/100)*int(n.substr(0,n.indexOf("%")));
					else if(n.indexOf("px")>0 || int(n)>0)
						return (n.indexOf("px")>0) ? 
								int(n.substr(0, n.indexOf("px"))) : 
								int(n);
				break;
			}
			
			return 0;
		}
		
		private function addToList(list:Array, obj:DisplayObject, options:Object):void{
			var found:Boolean=false;
			
			list.some(function(item:Object, index:int, arr:Array):Boolean{
				if(item.obj == obj){
					arr[index] = options;
					arr[index].obj = obj;
					found=true;
					return true;
				}
				
				return false;
			});
			
			if(!found){
				var o:Object = options;
				o.obj = obj;
				list.push(o);
			}
		}
		
		
		/* *** OLD(er) Stuff *** */
		public function manualTrigger():void{
			resized();
		}
		
		/*
		private function position(obj:Object):void{
			var stage:Stage = this.stageHandler.get() as Stage;
			
			var w:int = (obj.po) ? obj.po.width : (stage) ? stage.stageWidth : 0;
			var h:int = (obj.po) ? obj.po.height: (stage) ? stage.stageHeight : 0;
			
			// When a parent object is given, confirm to see if
			// it's also an object that is being watched by this
			// ResizeHandler. If true, update the parent before
			// the object at hand.
			// 
			// Note: The current iteration most likely will call
			// for some unnecessary repeats, but for now it's
			// good enough. 
			if(obj.po && g(obj.po)){
				position(g(obj.po));
			}
			
			// Specify all possible positioning
			// options, customized for this specific
			// object.
			var options:Object  = {top: 		{y:0},
								   bottom: 		{y:h-obj.o.height},
								   right:		{x:w-obj.o.width},
								   left: 		{x:0},
								   topLeft: 	{x:0, y:0},
								   topRight: 	{x:w-obj.o.width, y:0},
								   bottomleft: 	{x:0, y:h-obj.o.height},
								   bottomRight: {x:w-obj.o.width, y:h-obj.o.height},
								   center:		{x:(w/2)-(obj.o.width/2), y:(h/2)-(obj.o.height/2)},
								   middle:		{y:(h/2)-(obj.o.height/2)},
								   hormiddle:	{x:(w/2)-(obj.o.width/2)},
								   
								   // Size-related options
								   fullHeight:	{height: h},
								   fullWidth:	{width: w},
								   full:		{width: w, height: h, x: 0, y: 0},
								   halfHeight:	{height: Math.floor(h/2)},
								   halfWidth:	{width: Math.floor(w/2)}
								   };
			
			
			if(obj.p && obj.p.substr(obj.p.length-1) == "%" && obj.p.indexOf("=") > 0){
				var varName:String = obj.p.substring(0,obj.p.indexOf("="));
				var varValue:* = obj.p.substring(obj.p.indexOf("=")+1,obj.p.length-1);
				
				if(varName && varValue){
					switch(varName){
						case "width":
							obj.o[varName] = Math.ceil((w/100)*varValue);
						break;
						case "height":
							obj.o[varName] = Math.ceil((h/100)*varValue);
						break;
						default:
							obj.o[varName] = varValue+'%';
						break;
					}
				}
			}
			
			// Check if it has a known position
			// specified, and if so use it.
			if(obj.p && typeof options[obj.p] != "undefined"){
				if(options[obj.p].x) obj.o.x = options[obj.p].x;
				if(options[obj.p].y) obj.o.y = options[obj.p].y;
				if(options[obj.p].height) obj.o.height = options[obj.p].height;
				if(options[obj.p].width) obj.o.width = options[obj.p].width;
			}
		}
		
		public function a(object:*, pos:*, parentObj:Object=undefined):ResizeHandler{
			if(pos is Array){
				for(var i:int=0; i < pos.length; i++){
					a(object, pos[i], parentObj);
				}
			}
			else{
				var n:Object = {o:object, p:pos, po:parentObj};
				objects.addItem(n);
				position(n);
			}
			
			return this;
		}
		
		public function g(object:*):*{
			for(var i:int=0; i < objects.length; i++){
				if(objects[i].o == object){
					return objects[i];
				}
			}
			
			return undefined;
		}
		
		public function d(object:*):void{
			
		}
		*/
	}
}